{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Backtest (low-level API)\n",
    "\n",
    "Tutorial for [NautilusTrader](https://nautilustrader.io/docs/) a high-performance algorithmic trading platform and event driven backtester.\n",
    "\n",
    "[View source on GitHub](https://github.com/nautechsystems/nautilus_trader/blob/develop/docs/getting_started/backtest_low_level.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "This tutorial walks through how to use a `BacktestEngine` to backtest a simple EMA cross strategy\n",
    "with a TWAP execution algorithm on a simulated Binance Spot exchange using historical trade tick data.\n",
    "\n",
    "The following points will be covered:\n",
    "- Load raw data (external to Nautilus) using data loaders and wranglers.\n",
    "- Add this data to a `BacktestEngine`.\n",
    "- Add venues, strategies, and execution algorithms to a `BacktestEngine`.\n",
    "- Run backtests with a `BacktestEngine`.\n",
    "- Perform post-run analysis and repeated runs.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "- Python 3.11+ installed.\n",
    "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`).\n",
    "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## Imports\n",
    "\n",
    "We'll start with all of our imports for the remainder of this tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from decimal import Decimal\n",
    "\n",
    "from nautilus_trader.backtest.config import BacktestEngineConfig\n",
    "from nautilus_trader.backtest.engine import BacktestEngine\n",
    "from nautilus_trader.examples.algorithms.twap import TWAPExecAlgorithm\n",
    "from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAP\n",
    "from nautilus_trader.examples.strategies.ema_cross_twap import EMACrossTWAPConfig\n",
    "from nautilus_trader.model import BarType\n",
    "from nautilus_trader.model import Money\n",
    "from nautilus_trader.model import TraderId\n",
    "from nautilus_trader.model import Venue\n",
    "from nautilus_trader.model.currencies import ETH\n",
    "from nautilus_trader.model.currencies import USDT\n",
    "from nautilus_trader.model.enums import AccountType\n",
    "from nautilus_trader.model.enums import OmsType\n",
    "from nautilus_trader.persistence.wranglers import TradeTickDataWrangler\n",
    "from nautilus_trader.test_kit.providers import TestDataProvider\n",
    "from nautilus_trader.test_kit.providers import TestInstrumentProvider"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "## Loading data\n",
    "\n",
    "For this tutorial we use stub test data from the NautilusTrader repository (the automated test suite also uses this data to verify platform correctness).\n",
    "\n",
    "First, instantiate a data provider to read raw CSV trade tick data into memory as a `pd.DataFrame`.\n",
    "Next, initialize the instrument that matches the data (in this case the `ETHUSDT` spot cryptocurrency pair for Binance) and reuse it for the remainder of the backtest run.\n",
    "\n",
    "Then wrangle the data into a list of Nautilus `TradeTick` objects so you can add them to the `BacktestEngine`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load stub test data\n",
    "provider = TestDataProvider()\n",
    "trades_df = provider.read_csv_ticks(\"binance/ethusdt-trades.csv\")\n",
    "\n",
    "# Initialize the instrument which matches the data\n",
    "ETHUSDT_BINANCE = TestInstrumentProvider.ethusdt_binance()\n",
    "\n",
    "# Process into Nautilus objects\n",
    "wrangler = TradeTickDataWrangler(instrument=ETHUSDT_BINANCE)\n",
    "ticks = wrangler.process(trades_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "See the [Loading External Data](https://nautilustrader.io/docs/latest/concepts/data#loading-data) guide for a more detailed explanation of the typical data processing components and pipeline."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "## Initialize a backtest engine\n",
    "\n",
    "Create a backtest engine. You can call `BacktestEngine()` to instantiate an engine with the default configuration.\n",
    "\n",
    "We also initialize a `BacktestEngineConfig` (with only a custom `trader_id` specified) to illustrate the general configuration pattern.\n",
    "\n",
    "See the [Configuration](https://nautilustrader.io/docs/api_reference/config) API reference for details of all configuration options available.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configure backtest engine\n",
    "config = BacktestEngineConfig(trader_id=TraderId(\"BACKTESTER-001\"))\n",
    "\n",
    "# Build the backtest engine\n",
    "engine = BacktestEngine(config=config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "## Add venues\n",
    "\n",
    "Create a venue to trade on that matches the market data you add to the engine.\n",
    "\n",
    "In this case we set up a simulated Binance Spot exchange.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add a trading venue (multiple venues possible)\n",
    "BINANCE = Venue(\"BINANCE\")\n",
    "engine.add_venue(\n",
    "    venue=BINANCE,\n",
    "    oms_type=OmsType.NETTING,\n",
    "    account_type=AccountType.CASH,  # Spot CASH account (not for perpetuals or futures)\n",
    "    base_currency=None,  # Multi-currency account\n",
    "    starting_balances=[Money(1_000_000.0, USDT), Money(10.0, ETH)],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## Add data\n",
    "\n",
    "Add data to the backtest engine. Start by adding the `Instrument` object we initialized earlier to match the data.\n",
    "\n",
    "Then add the trades we wrangled earlier.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add instrument(s)\n",
    "engine.add_instrument(ETHUSDT_BINANCE)\n",
    "\n",
    "# Add data\n",
    "engine.add_data(ticks)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    ":::note\n",
    "Machine resources and your imagination limit the amount and variety of data types you can use (custom types are possible).\n",
    "You can also backtest across multiple venues, again constrained only by machine resources.\n",
    ":::\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "## Add strategies\n",
    "\n",
    "Add the trading strategies you plan to run as part of the system.\n",
    "\n",
    ":::note\n",
    "You can backtest multiple strategies and instruments; machine resources remain the only limit.\n",
    ":::\n",
    "\n",
    "First, initialize a strategy configuration, then use it to initialize a strategy that you can add to the engine:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configure your strategy\n",
    "strategy_config = EMACrossTWAPConfig(\n",
    "    instrument_id=ETHUSDT_BINANCE.id,\n",
    "    bar_type=BarType.from_str(\"ETHUSDT.BINANCE-250-TICK-LAST-INTERNAL\"),\n",
    "    trade_size=Decimal(\"0.10\"),\n",
    "    fast_ema_period=10,\n",
    "    slow_ema_period=20,\n",
    "    twap_horizon_secs=10.0,\n",
    "    twap_interval_secs=2.5,\n",
    ")\n",
    "\n",
    "# Instantiate and add your strategy\n",
    "strategy = EMACrossTWAP(config=strategy_config)\n",
    "engine.add_strategy(strategy=strategy)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "You may notice that this strategy config includes parameters related to a TWAP execution algorithm.\n",
    "We can flexibly use different parameters per order submit, but we still need to initialize and add the actual `ExecAlgorithm` component that executes the algorithm.\n",
    "\n",
    "## Add execution algorithms\n",
    "\n",
    "NautilusTrader enables you to build complex systems of custom components. Here we highlight one built-in component: a TWAP execution algorithm. Configure it and add it to the engine using the same general pattern as for strategies.\n",
    "\n",
    ":::note\n",
    "You can backtest multiple execution algorithms; machine resources remain the only limit.\n",
    ":::\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate and add your execution algorithm\n",
    "exec_algorithm = TWAPExecAlgorithm()  # Using defaults\n",
    "engine.add_exec_algorithm(exec_algorithm)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "## Run backtest\n",
    "\n",
    "After configuring the data, venues, and trading system, run a backtest.\n",
    "Call the `.run(...)` method to process all available data by default.\n",
    "\n",
    "See the [BacktestEngineConfig](https://nautilustrader.io/docs/latest/api_reference/config) API reference for a complete description of all available methods and options.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the engine (from start to end of data)\n",
    "engine.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "## Post-run and analysis\n",
    "\n",
    "After the backtest completes, the engine automatically logs a post-run tearsheet with default statistics (or custom statistics that you load; see the advanced [Portfolio statistics](../concepts/portfolio.md#portfolio-statistics) guide).\n",
    "\n",
    "The engine also keeps many data and execution objects in memory, which you can use to generate additional reports for performance analysis.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "engine.trader.generate_account_report(BINANCE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "engine.trader.generate_order_fills_report()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "engine.trader.generate_positions_report()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25",
   "metadata": {},
   "source": [
    "## Repeated runs\n",
    "\n",
    "You can reset the engine for repeated runs with different strategy and component configurations.\n",
    "\n",
    "Instruments and data persist across resets by default, so you don't need to reload them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "# For repeated backtest runs, reset the engine\n",
    "engine.reset()\n",
    "\n",
    "# Instruments and data persist, just add new components and run again"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27",
   "metadata": {},
   "source": [
    "Remove and add individual components (actors, strategies, execution algorithms) as required.\n",
    "\n",
    "See the [Trader](../api_reference/trading.md) API reference for a description of all methods available to achieve this.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Once done, good practice to dispose of the object if the script continues\n",
    "engine.dispose()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
